/**
 * ex11. ☆goroutine channel 实现并发和并行
 *
 * @author cyy
 * @date 2020/11/12
 */
package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

var wg sync.WaitGroup
var mutex sync.Mutex
var rwLock sync.RWMutex

var count int

func main() {
	fmt.Println("============================ goroutine 协程 ============================")
	/*
		golang中的主线程
			在一个golang程序的主线程上可以起多个协程，golang中多协程可以实现并发或者并行
			当主线程执行完毕后即使协程没有执行完毕，程序也会退出
		协程 —— 用户级线程，对内核透明，系统并不知道有协程的存在，完全由用户自己的程序进行调度
			golang的一大特色：从语言层面原生支持协程，表现形式就是goroutine
			golang中的goroutine（协程） vs 线程：
				默认占用内存远比Java、C的线程少 —— os线程一般都有固定的栈内存（2mb左右），一个goroutine占用内存非常小（2kb左右）
				多协程goroutine切换调度开销方面远比线程要少
	*/

	// goroutine的简单使用
	wg.Add(1)          // 协程计数器 +1
	go goroutineTest() // 开启一个协程
	wg.Wait()          // 等待协程执行完毕
	fmt.Println("main 退出")

	// 使用defer + recover捕获协程中的异常
	fmt.Println("\n---------------------------- channel中的异常处理 ----------------------------")
	wg.Add(2)
	go errFunc()
	go healthyFunc()
	wg.Wait()

	fmt.Println("\n============================ channel 管道 ============================")
	/*
		channel 管道
			语言级别上提供的goroutine间的通讯方式，可以使用channel在多个goroutine之间传递消息，是一种通信机制。
			go语言的并发模型是CSP(Communicating Sequential Processes)，提倡通过通信共享内存而不是通过共享内存实现通信。
			go中的管道（channel）是一种特殊的类型，且是一种引用类型，遵循fifo先入先出的规则，保证收发数据的顺序，每一个管道都是一个具体类型的导管，声明时需要为其指定元素类型。
	*/
	fmt.Println("\n---------------------------- 基本使用 ----------------------------")
	// 管道的基本使用
	// 	通过chan关键字声明，通过make函数初始化 —— make(chan 元素类型, 容量)
	channel := make(chan int, 2)
	channel <- 1
	channel <- 2
	// channel <- 3 // 超出容量 -> 管道阻塞	all goroutines are asleep - deadlock!
	// 值：0xc00010a000，类型：chan int，容量：3，长度：2
	fmt.Printf("值：%v，类型：%T，容量：%v，长度：%v\n", channel, channel, cap(channel), len(channel))
	a := <-channel
	fmt.Println(a)         // 1
	fmt.Println(<-channel) // 2
	// fmt.Println(<- channel)	// 超取 -> 管道阻塞	all goroutines are asleep - deadlock!

	for i := 0; i < 2; i++ {
		channel <- i
	}

	/* 管道的遍历 */
	// fatal error: all goroutines are asleep - deadlock!
	// 使用for range遍历管道，当通道被关闭的时候就会退出for range，如果没有关闭管道就会报错。普通的for循环无需关闭
	/*
		for i := 0; i < 2; i++ {
			fmt.Println(<-channel)
		}
	*/
	close(channel) // 关闭管道
	for v := range channel {
		fmt.Println(v)
	}

	fmt.Println("\n---------------------------- 单向管道 ----------------------------")
	/*
		单向管道 —— 只写 or 只读 的管道
			默认情况下是双向的，可读可写
	*/
	// 只写管道：wch := make(chan<- int, 2)
	// 只读管道：rch := make(<-chan int, 2)

	/* goroutine 结合 channel 实现并行读写 */
	ch := make(chan int, 10)
	wg.Add(2)
	go write(ch)
	go read(ch)
	wg.Wait()

	fmt.Println("\n---------------------------- select 多路复用 ----------------------------")
	/*
		select多路复用：
			开启多个协程实现同时从多个通道接收数据的需求，其运行性能会差很多，可以使用select-case
			每个case对应一个管道的通信过程，select会一直等待，直到某个case的通信操作完成时，就会执行case分支对应的语句
	*/
	intChan := make(chan int, 5)
	floatChan := make(chan float64, 5)
	for i := 0; i < 5; i++ {
		intChan <- i
		floatChan <- float64(i) + 0.1
	}
	// 使用select来获取channel中的数据时无需关闭channel
label:
	for {
		select {
		case v := <-intChan:
			fmt.Printf("【intChan】%v\n", v)
			time.Sleep(time.Millisecond * 50)
		case v := <-floatChan:
			fmt.Printf("【floatChan】%v\n", v)
			time.Sleep(time.Millisecond * 50)
		default:
			fmt.Println("【over】")
			break label // 跳出死循环
		}
	}

	fmt.Println("\n============================ lock 锁 ============================")
	/*
		互斥锁
			由sync包中的Mutex结构体类型表示，两个公开的指针方法Lock和Unlock
		其他
			rwLock.Lock()	// 写锁
			rwLock.Unlock()
			rwLock.RLock()	// 读锁
			rwLock.RUnlock()
	*/
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go mutexTest()
	}
	wg.Wait()

	fmt.Println("\n============================ 获取cpu数量 ============================")
	/*
		获取cpu数量，设置golang并行运行时占用的核数
	*/
	cpuNum := runtime.NumCPU()   // 相当于java中的 Runtime.getRuntime().availableProcessors()
	fmt.Println("cpu数量", cpuNum) // 12
}

func errFunc() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("【errFunc】执行失败，异常已捕获")
		}
		wg.Done()
	}()
	var myMap map[int]int
	myMap[0] = 0
}

func healthyFunc() {
	defer wg.Done()
	time.Sleep(time.Millisecond * 50)
	fmt.Println("【healthyFunc】执行成功")
}

func goroutineTest() {
	defer wg.Done()
	for i := 0; i < 10; i++ {
		fmt.Println("goroutineTest1")
		time.Sleep(time.Millisecond * 100)
	}
	/*
		wg.Done() 相当于 wg.Add(-1)，源码如下：
		func (wg *WaitGroup) Done() {
			wg.Add(-1)
		}
	*/
	// wg.Done() // 计数器 -1，如果没有这行代码：all goroutines are asleep - deadlock!
}

func write(ch chan int /* 可使用单向管道（只写）：ch chan<- int */) {
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Printf("【写入】%v\n", i)
		time.Sleep(time.Millisecond * 50)
	}
	close(ch)
	wg.Done()
}

func read(ch chan int /* 可使用单向管道（只读）：ch <-chan int */) {
	for v := range ch {
		fmt.Printf("【读取】%v\n", v)
		time.Sleep(time.Millisecond * 50)
	}
	wg.Done()
}

func mutexTest() {
	mutex.Lock()
	count++
	fmt.Println("count =", count)
	wg.Done()
	mutex.Unlock()
}
